/*
 * $Id: list.c,v 1.9 2006-02-07 07:23:30 bacon Exp $
 */

#include <xp/bas/list.h>
#include <xp/bas/memory.h>
#include <xp/bas/assert.h>

/* TODO: finish this.... */
static xp_list_chunk_t* __add_chunk (xp_list_t* list);

xp_list_t* xp_list_open (
	xp_list_t* list, xp_size_t capacity,
	xp_size_t datum_size, xp_list_callback_t* callback)
{
	if (list == XP_NULL) {
		list = (xp_list_t*)xp_malloc (xp_sizeof(xp_list_t));
		if (list == XP_NULL) return XP_NULL;
		list->__dynamic = xp_true;
	}
	else list->__dynamic = xp_false;

	list->datum_size = datum_size;
	list->size = 0;
	list->callback = callback;

	list->head = XP_NULL;
	list->tail = XP_NULL;

	list->free = XP_NULL;
	list->chunk = XP_NULL;

	if (capacity <= 0) {
		/* adjust the chunk_size but don't allocate a chunk yet */
		list->capacity = 0;
		list->chunk_size = 1;
	}
	else {
		list->capacity = capacity;
		list->chunk_size = capacity;
		if (__add_chunk (list) == XP_NULL) {
			if (list->__dynamic) xp_free (list);
			return XP_NULL;
		}
	}

	if (list->callback != XP_NULL &&
	    list->callback->initialize != XP_NULL) {
		list->callback->initialize (list);
	}

	return list;
}

void xp_list_close (xp_list_t* list)
{
	xp_list_chunk_t* chunk, * tmp;

	if (list->callback != XP_NULL &&
	    list->callback->finalize != XP_NULL) {
		list->callback->finalize (list);
	}

	chunk = list->chunk;
	while (chunk != XP_NULL) {
		tmp = chunk->next;
		xp_free (chunk);
		chunk = tmp;
	}

	if (list->__dynamic) xp_free (list);
}

void xp_list_set_chunk_size (xp_list_t* list, xp_size_t chunk_size)
{
	if (chunk_size <= 0) chunk_size = 1;
	list->chunk_size = chunk_size;
}

void* xp_list_append (xp_list_t* list, const void* value)
{
	xp_list_link_t* link;

	if (list->free == XP_NULL && 
	    __add_chunk (list) == XP_NULL) return XP_NULL;

	xp_assert (list->free != XP_NULL);

	link = list->free;
	list->free = link->next;	

	xp_memcpy (link + 1, value, list->datum_size);	

	if (list->head == XP_NULL) {
		xp_assert (list->tail == XP_NULL);
		link->next = link->prev = XP_NULL;
		list->head = list->tail = link;	
	}
	else {
		xp_assert (list->tail != XP_NULL);

		list->tail->next = link;
		link->prev = list->tail;
		link->next = XP_NULL;
		list->tail = link;
	}

	return link;
}

xp_size_t xp_list_walk (
	xp_list_t* list, xp_ssize_t (*func) (xp_list_t*, void*))
{
	xp_size_t count = 0;
	xp_list_link_t* link = list->head;

	while (link != XP_NULL) {
		count++;
		func (list, link + 1);
		/* TODO */

		link = link->next;
	}

	return count;
}

/*
static void __free_link (xp_list_t* list, xp_list_link_t* link)
{
	link->next = list->free;
	list->free = link;
}
*/

static xp_list_chunk_t* __add_chunk (xp_list_t* list)
{
	xp_list_chunk_t* chunk;
	xp_list_link_t* p, *start, * end;
	xp_size_t link_bytes, chunk_bytes;

	link_bytes = 
		xp_sizeof(xp_list_link_t) +
		list->datum_size;
	chunk_bytes = 
		xp_sizeof(xp_list_chunk_t) + 
		list->chunk_size * link_bytes;
	
	chunk = (xp_list_chunk_t*) xp_malloc (chunk_bytes);
	if (chunk == XP_NULL) return XP_NULL;

	/* weave the free list */
	start = (xp_list_link_t*)(chunk + 1);
	end = (xp_list_link_t*)((xp_byte_t*)chunk + chunk_bytes - link_bytes);

	for (p = start; p < end; ) {
		p->next = (xp_list_link_t*)(((xp_byte_t*)p) + link_bytes);
		p = p->next;
	}
	p->next = list->free;
	list->free = start;

	chunk->next = list->chunk;
	list->chunk = chunk;

	return chunk;
}

